Vue-router使用demo
<div id="app">
<h1>Hello App!</h1>
<p>
<router-link to="/foo">Go to Foo</router-link>
<router-link to="/bar">Go to Bar</router-link>
</p>
<router-view></router-view>
</div>
const Foo = { template: '<div>foo</div>' }
const Bar = { template: '<div>bar</div>' }
const routes = [
{ path: '/foo', component: Foo },
{ path: '/bar', component: Bar }
]
Vue.use(VueRouter)
const router = new VueRouter({
routes
})
const app = new Vue({
router
}).$mount('#app')
new VueRouter 发生了什么?
VueRouter
是index.js
暴露的一个类, 所以我们看看他的构造函数都做了什么:
constructor (options: RouterOptions = {}) {
this.app = null
this.apps = []
this.options = options // 开发者传入的 { routes } 对象
this.beforeHooks = [] // beforeEach钩子函数
this.resolveHooks = [] // beforeResolve钩子函数
this.afterHooks = [] // afterEach钩子函数
// 生成matcher对象, 包含两个方法 {addRoutes, match}
this.matcher = createMatcher(options.routes || [], this)
// mode默认是hash
let mode = options.mode || 'hash'
// IE9 自动降级成hash模式
this.fallback = mode === 'history' && !supportsPushState && options.fallback !== false
if (this.fallback) {
mode = 'hash'
}
if (!inBrowser) {
mode = 'abstract'
}
this.mode = mode
// 根据开发者传入的mode, 构造不同的类实例赋值给this.history
switch (mode) {
case 'history':
this.history = new HTML5History(this, options.base)
break
case 'hash':
this.history = new HashHistory(this, options.base, this.fallback)
break
case 'abstract':
this.history = new AbstractHistory(this, options.base)
break
default:
if (process.env.NODE_ENV !== 'production') {
assert(false, `invalid mode: ${mode}`)
}
}
}
显而易见, 构造函数主要是做了一些类属性的初始化工作, 包括生成matcher
对象, 以及根据mode
初始化相应的history类, 这里暂时不探究着这些属性有何意义, 我们只需知道, 开发者按规范传入了一个{routes}
对象, 经过一系列的操作, 我们会得到一个VueRouter
实例router
, 而后, 我们将router
传入到new Vue的初始化配置项中, 了解vue
的vm.$options
属性的同学都知道, 我们可以在根实例通过this.$options.router
取得router
, 而这一步的实现跟vue.use(VueRouter)有着莫大的干系.
install函数
众所周知, vue.use
是vue
使用插件的方式, vue
会调用插件的install
方法, 而VueRouter的install 函数长啥样呢?
注: 为方便阅读, 代码有删减
export function install (Vue) {
// 重复安装检测
if (install.installed && _Vue === Vue) return
install.installed = true
// 小技巧, _Vue保留Vue的引用, 将_Vue导出, 整个项目即可使用Vue
_Vue = Vue
// 全局混入beforeCreate钩子函数
Vue.mixin({
---
})
Object.defineProperty(Vue.prototype, '$router', {
get () { return this._routerRoot._router }
})
Object.defineProperty(Vue.prototype, '$route', {
get () { return this._routerRoot._route }
})
Vue.component('RouterView', View)
Vue.component('RouterLink', Link)
}
install函数它干了一件很重要的事, Vue.mixin(...)
, 全局混入了一个钩子函数, 每个组件都会走一遍这个钩子函数, 而这个钩子函数做了些什么呢?
beforeCreate () {
// 初始化根实例上的router, 并定义响应式的_route
if (isDef(this.$options.router)) {
this._routerRoot = this
this._router = this.$options.router
this._router.init(this)
Vue.util.defineReactive(this, '_route', this._router.history.current)
} else {
this._routerRoot = (this.$parent && this.$parent._routerRoot) || this
}
}
我们知道只有根实例上才有this.$options.router
, 所以根实例初始化的时候, if条件 成立, 就会走到里面的代码:
-
this._routerRoot
就是根实例; -
this._router
就是router
(VueRoute
实例); - 调用
Vue.util.defineReactive
在根实例上注册了一个响应式的属性_route
; - 调用
router
实例上的init
方法(这个以后再讲).
接着, 当其他vue实例初始化的时候, 例如, 我们一般会使用的<App/>
组件, 在它的$options上
就没有router
属性, 所以它会走到else逻辑: 如果当前存在父实例, 则this._routerRoot
等于this.$parent._routerRoot
, 不存在的话, this._routerRoot
就指向当前实例, 这就意味着, 所有实例的_routerRoot
在经历过beforeCreate
这个钩子函数以后, 实例的this._routerRoot
都会指向根实例的this
, 也就是说所有实例的this._routerRoot
都保留了对根实例的引用. 这有什么用呢? 且看以下代码:
Object.defineProperty(Vue.prototype, '$router', {
get () { return this._routerRoot._router }
})
Object.defineProperty(Vue.prototype, '$route', {
get () { return this._routerRoot._route }
})
答案已经显而易见, 通过es5的Object.defineProperty
, 在Vue.prototype
上定义了$router
的取值函数, 返回根实例上的_router
, 也就是VueRouter
的实例router
, 还定义了$route
的取值函数, 返回的是根实例上的_route
, 因此, 我们可以在任何一个组件使用this.$router
就可以拿到router
对象, 可以使用router
上的方法、属性.
最后, intall 方法还注册了两个组件, <router-view/>
和<router-link/>
:
Vue.component('RouterView', View)
Vue.component('RouterLink', Link)
下回分解
createMatcher
函数实现分析.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。